package com.tender.desensitization;

import com.google.common.collect.Lists;
import com.tender.desensitization.annotations.GlobalEncryptionTag;
import com.tender.desensitization.annotations.GlobalDecryptionTag;
import com.tender.desensitization.annotations.FieldEncryptionTag;
import com.tender.desensitization.annotations.FieldDecryptionTag;
import com.tender.desensitization.factory.field.*;
import com.tender.desensitization.factory.instance.DecryptionUserWrapperProcessFactory;
import com.tender.desensitization.factory.instance.EncryptionUserWrapperProcessFactory;
import com.tender.desensitization.factory.instance.TypeProcessFactory;
import com.tender.desensitization.factory.instance.TypeProcessFactoryFacade;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ClassUtils;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.MethodParameter;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@Slf4j
@RestControllerAdvice
public class GlobalRequestResponseAdvice extends SensitiveResponseBodyAdvice implements InitializingBean {

    private final Map<Class<?>, Boolean> decryptionFieldMap = new ConcurrentHashMap<>();

    private final Map<Class<?>, Boolean> encryptionFieldMap = new ConcurrentHashMap<>();

    /**
     * 解决 类型嵌套问题
     */
    public static final Map<Class<?>, List<Class<?>>> circleMap = new ConcurrentHashMap<>();

    /**
     * 字段级别解密
     */
    private final TypeProcessFactoryFacade decryptionFactoryCollection = new TypeProcessFactoryFacade();

    /**
     * 字段级别加密
     */
    private final TypeProcessFactoryFacade encryptionFactoryCollection = new TypeProcessFactoryFacade();

    /**
     * 判断是否需要字段级别"解密"--Field 级别
     */
    private final FieldAnnotationDetectionFactoryFacade decryption4FieldFactoryCollection = new FieldAnnotationDetectionFactoryFacade();

    /**
     * 判断是否需要字段级别"加密"--Field 级别
     */
    private final FieldAnnotationDetectionFactoryFacade encryption4FieldFactoryCollection = new FieldAnnotationDetectionFactoryFacade();

    @Override
    protected boolean requestParamNeedDecryption(MethodParameter methodParameter) {
        return hasAnnotation(methodParameter, GlobalDecryptionTag.class);
    }

    @Override
    protected boolean responseParamNeedEncryption(MethodParameter methodParameter) {
        return hasAnnotation(methodParameter, GlobalEncryptionTag.class);
    }

    public boolean hasAnnotation(MethodParameter methodParameter, Class<? extends Annotation> clazz) {
        boolean flag = methodParameter.hasMethodAnnotation(clazz) || methodParameter.hasParameterAnnotation(clazz);
        if (!flag) {
            Method method = methodParameter.getMethod();
            if (null != method) {
                flag = null != method.getDeclaringClass().getAnnotation(clazz);
            }
        }
        return flag;
    }

    /**
     * @see FieldDecryptionTag
     * 判定是否存在该注解
     */
    @Override
    public boolean hasFieldDecryptionAnnotation(MethodParameter methodParameter) {
        if (null != methodParameter) {
            Class<?> clazz = methodParameter.getParameterType();
            Boolean flag = decryptionFieldMap.get(clazz);
            if (null != flag) {
                return flag;
            }
            // 并发访问，这边只针对 Class<?> 加锁，处理完毕后，会把结果落入缓存，因此压力不大。
            synchronized (clazz) {
                try {
                    List<Field> fields = FieldReflectUtils.getAllFields(clazz);
                    // 顶级 Class<?>
                    if (!CollectionUtils.isEmpty(fields)) {
                        List<Class<?>> list = Lists.newArrayList(clazz);
                        // 顶级 Class<?>
                        circleMap.put(clazz, list);
                        TopClassContext.setTopClass(clazz);
                    }
                    for (Field field : fields) {
                        FieldAnnotationDetectionFactory processFactory = decryption4FieldFactoryCollection.getProcessFactory(field.getType());
                        if (null != processFactory) {
                            if (processFactory.processType(field)) {
                                decryptionFieldMap.put(clazz, true);  // 加入缓存
                                return true;
                            }
                        }
                    }

                    decryptionFieldMap.put(clazz, false); // 加入缓存
                } finally {
                    circleMap.remove(clazz);              // 清理顶级 Class<?>
                    TopClassContext.clear();
                }
            }
        }

        return false;
    }

    /**
     * @see FieldEncryptionTag
     * 判定是否存在该注解
     */
    @Override
    public boolean hasFieldEncryptionAnnotation(Object instance) {
        if (null != instance) {
            Class<?> clazz = instance.getClass();

            // 处理顶层类是 List、Map、Set 类型的 TODO 泛型被擦除了
            if (getFunction().apply(clazz)) {
                // 如果是集合类型
                // clazz = FieldReflectUtils.getGenericClassWithInCollection(clazz);
                // 执行放行，让后续逻辑尝试字段级别的加密处理
                return true;
            }

            if (null == clazz || ClassUtils.isPrimitiveOrWrapper(clazz) || String.class == clazz) {
                return false;
            }

            Boolean flag = encryptionFieldMap.get(clazz);
            if (null != flag) {
                return flag;
            }

            synchronized (clazz) {
                try {
                    List<Field> fields = FieldReflectUtils.getAllFields(clazz);
                    // 顶级 Class<?>
                    if (!CollectionUtils.isEmpty(fields)) {
                        List<Class<?>> list = Lists.newArrayList(clazz);
                        // 顶级 Class<?>
                        circleMap.put(clazz, list);
                        TopClassContext.setTopClass(clazz);
                    }
                    for (Field field : fields) {
                        FieldAnnotationDetectionFactory processFactory = encryption4FieldFactoryCollection.getProcessFactory(field.getType());
                        if (null != processFactory) {
                            if (processFactory.processType(field)) {
                                encryptionFieldMap.put(clazz, true);  // 加入缓存
                                return true;
                            }
                        }
                    }

                    encryptionFieldMap.put(clazz, false); // 加入缓存
                } finally {
                    circleMap.remove(clazz);              // 清理顶级 Class<?>
                    TopClassContext.clear();
                }
            }
        }

        return false;
    }

    @Override
    public Object doFieldEncryption(Object data) {
        if (null != data) {
            TypeProcessFactory processFactory = encryptionFactoryCollection.getProcessFactory(data.getClass());
            if (null != processFactory) {
                processFactory.processType(data);
            }
        }

        return data;
    }

    @Override
    public Object doFieldDecryption(Object data, String keyAES128) {
        if (null != data) {
            TypeProcessFactory processFactory = decryptionFactoryCollection.getProcessFactory(data.getClass());
            if (null != processFactory) {
                processFactory.processType(data);
            }
        }

        return data;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        decryption4FieldFactoryCollection.setTypeProcessFactory(new FieldDecryptionTagStringDetectionFactory(decryption4FieldFactoryCollection));
        encryption4FieldFactoryCollection.setTypeProcessFactory(new FieldEncryptionTagStringDetectionFactory(encryption4FieldFactoryCollection));
        decryptionFactoryCollection.setTypeProcessFactory(new DecryptionUserWrapperProcessFactory(decryptionFactoryCollection));
        encryptionFactoryCollection.setTypeProcessFactory(new EncryptionUserWrapperProcessFactory(encryptionFactoryCollection));

        super.afterPropertiesSet();
    }

}
